import { $set } from '@feng3d/serialization';
import { BoxGeometry3D, BufferAttribute3D, BufferGeometry3D, Euler, Intersection3D, Line3D, LineBasicMaterial3D, Mesh3D, MeshNormalMaterial3D, MeshPhongMaterial3D, Node3D, PerspectiveCamera3D, Raycaster3D, Scene3D, StatsComponent, TextureLoader3D, Vector2, Vector3, WebGLRenderer3D } from 'feng3d-three';
import GUI from 'lil-gui';
import '../../extension/controls/OrbitControls3D';
import { DecalGeometry3D } from '../../extension/geometries/DecalGeometry3D';
import { GLTFLoader3D } from '../../extension/loaders/GLTFLoader3D';

const rootPath = '../../';

const container = document.getElementById('container');

let renderer: WebGLRenderer3D;
let scene: Scene3D;
let camera: PerspectiveCamera3D;
let stats: StatsComponent;
let mesh: Mesh3D;
let raycaster: Raycaster3D;
let line: Line3D;

const intersection = {
    intersects: false,
    point: new Vector3(),
    normal: new Vector3()
};
const mouse = new Vector2();
const intersects: Intersection3D[] = [];

const textureLoader = new TextureLoader3D();
const decalDiffuse = textureLoader.load(`${rootPath}textures/decal/decal-diffuse.png`);
const decalNormal = textureLoader.load(`${rootPath}textures/decal/decal-normal.jpg`);

const decalMaterial = $set(new MeshPhongMaterial3D(), {
    specular: { value: 0x444444 },
    map: decalDiffuse,
    normalMap: decalNormal,
    normalScale: new Vector2(1, 1),
    shininess: 30,
    transparent: true,
    depthTest: true,
    depthWrite: false,
    polygonOffset: true,
    polygonOffsetFactor: -4,
    wireframe: false
});

const decals: Mesh3D[] = [];
let mouseHelper: Mesh3D;
const position = new Vector3();
const orientation = new Euler();
const size = new Vector3(10, 10, 10);

const params = {
    minScale: 10,
    maxScale: 20,
    rotate: true,
    clear()
    {
        removeDecals();
    }
};

init();
animate();

function init()
{
    renderer = new Node3D().addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    stats = renderer.addComponent('StatsComponent');
    container.appendChild(stats.dom);

    scene = renderer.addComponent('Scene3D');

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 45, aspect: window.innerWidth / window.innerHeight, near: 1, far: 1000 });
    camera.entity.position.z = 120;

    const controls = scene.addComponent('OrbitControls3D', { camera, domElement: renderer.domElement });
    controls.minDistance = 50;
    controls.maxDistance = 200;

    scene.addComponent('AmbientLight3D', { color: { value: 0x443333 } });

    const dirLight1 = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0xffddcc }, intensity: 1 });
    dirLight1.entity.position.set(1, 0.75, 0.5);
    scene.entity.addChild(dirLight1.entity);

    const dirLight2 = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0xccccff }, intensity: 1 });
    dirLight2.entity.position.set(-1, 0.75, -0.5);
    scene.entity.addChild(dirLight2.entity);

    const geometry = new BufferGeometry3D();
    geometry.setFromPoints([new Vector3(), new Vector3()]);

    line = new Node3D().addComponent('Line3D', { geometry, material: new LineBasicMaterial3D() });
    scene.entity.addChild(line.entity);

    loadLeePerrySmith();

    raycaster = new Raycaster3D();

    mouseHelper = new Node3D().addComponent('Mesh3D', {
        geometry: $set(new BoxGeometry3D(), { width: 1, height: 1, depth: 10 }),
        material: new MeshNormalMaterial3D()
    });
    mouseHelper.entity.visible = false;
    scene.entity.addChild(mouseHelper.entity);

    window.addEventListener('resize', onWindowResize);

    let moved = false;

    controls.emitter.on('change', function ()
    {
        moved = true;
    });

    window.addEventListener('pointerdown', function ()
    {
        moved = false;
    });

    window.addEventListener('pointerup', function (event)
    {
        if (moved === false)
        {
            checkIntersection(event.clientX, event.clientY);

            if (intersection.intersects) shoot();
        }
    });

    window.addEventListener('pointermove', onPointerMove);

    function onPointerMove(event: PointerEvent)
    {
        if (event.isPrimary)
        {
            checkIntersection(event.clientX, event.clientY);
        }
    }

    function checkIntersection(x: number, y: number)
    {
        if (mesh === undefined) return;

        mouse.x = (x / window.innerWidth) * 2 - 1;
        mouse.y = -(y / window.innerHeight) * 2 + 1;

        raycaster.setFromCamera(mouse, camera);
        raycaster.intersectObject(mesh.entity, false, intersects);

        if (intersects.length > 0)
        {
            const p = intersects[0].point;
            mouseHelper.entity.position.copy(p);
            intersection.point.copy(p);

            const n = intersects[0].face.normal.clone();
            n.transformDirection(mesh.entity.matrixWorld);
            n.multiplyScalar(10);
            n.add(intersects[0].point);

            intersection.normal.copy(intersects[0].face.normal);
            mouseHelper.entity.lookAt(n);

            const positions = line.geometry.attributes.position as BufferAttribute3D;
            positions.setXYZ(0, p.x, p.y, p.z);
            positions.setXYZ(1, n.x, n.y, n.z);
            positions.needsUpdate = true;

            intersection.intersects = true;

            intersects.length = 0;
        }
        else
        {
            intersection.intersects = false;
        }
    }

    const gui = new GUI();

    gui.add(params, 'minScale', 1, 30);
    gui.add(params, 'maxScale', 1, 30);
    gui.add(params, 'rotate');
    gui.add(params, 'clear');
    gui.open();
}

function loadLeePerrySmith()
{
    const loader = new GLTFLoader3D();

    loader.load(`${rootPath}models/gltf/LeePerrySmith/LeePerrySmith.glb`, function (gltf)
    {
        mesh = gltf.scene.children[0].getComponent('Mesh3D');
        mesh.material = $set(new MeshPhongMaterial3D(), {
            specular: { value: 0x111111 },
            map: textureLoader.load(`${rootPath}models/gltf/LeePerrySmith/Map-COL.jpg`),
            specularMap: textureLoader.load(`${rootPath}models/gltf/LeePerrySmith/Map-SPEC.jpg`),
            normalMap: textureLoader.load(`${rootPath}models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg`),
            shininess: 25
        });

        scene.entity.addChild(mesh.entity);
        mesh.entity.scale.set(10, 10, 10);
    });
}

function shoot()
{
    position.copy(intersection.point);
    orientation.copy(mouseHelper.entity.rotation);

    if (params.rotate) orientation.z = Math.random() * 2 * Math.PI;

    const scale = params.minScale + Math.random() * (params.maxScale - params.minScale);
    size.set(scale, scale, scale);

    const material = decalMaterial.clone();
    material.color.setHex(Math.random() * 0xffffff);

    const m = new Node3D().addComponent('Mesh3D', { geometry: $set(new DecalGeometry3D(), { mesh, position, orientation, size }), material });

    decals.push(m);
    scene.entity.addChild(m.entity);
}

function removeDecals()
{
    decals.forEach(function (d)
    {
        scene.entity.removeChild(d.entity);
    });

    decals.length = 0;
}

function onWindowResize()
{
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate()
{
    requestAnimationFrame(animate);

    renderer.render(scene, camera);

    stats.update();
}
